import PointerTracker, { Pointer, InputEvent } from 'pointer-tracker';
import { Box } from '@chakra-ui/react';
import {
  Children,
  ReactNode,
  useEffect,
  forwardRef,
  memo,
  useRef,
  useState,
  useCallback,
  useImperativeHandle,
} from 'react';
interface Point {
  clientX: number;
  clientY: number;
}

interface ChangeOptions {
  /**
   * Fire a 'change' event if values are different to current values
   */
  allowChangeEvent?: boolean;
}

interface ApplyChangeOpts extends ChangeOptions {
  panX?: number;
  panY?: number;
  scaleDiff?: number;
  originX?: number;
  originY?: number;
}

export interface HandleTransformOpts extends ChangeOptions {
  scale?: number;
  x?: number;
  y?: number;
}

type ScaleRelativeToValues = 'container' | 'content';

export interface ScaleToOpts extends ChangeOptions {
  /** Transform origin. Can be a number, or string percent, eg "50%" */
  originX?: number | string;
  /** Transform origin. Can be a number, or string percent, eg "50%" */
  originY?: number | string;
  /** Should the transform origin be relative to the container, or content? */
  relativeTo?: ScaleRelativeToValues;
}

function getDistance(a: Point, b?: Point): number {
  if (!b) return 0;
  return Math.sqrt((b.clientX - a.clientX) ** 2 + (b.clientY - a.clientY) ** 2);
}

function getMidpoint(a: Point, b?: Point): Point {
  if (!b) return a;

  return {
    clientX: (a.clientX + b.clientX) / 2,
    clientY: (a.clientY + b.clientY) / 2,
  };
}

function createMatrix(): DOMMatrix {
  return new DOMMatrix();
}

const MIN_SCALE = 0.01;
const MAX_SCALE = 100000;

type PinchZoomProps = {
  children: ReactNode;
  onChange?: () => void;
};

const PinchZoom = forwardRef(
  ({ children, onChange }: PinchZoomProps, forwardedRef) => {
    // 只允许一个子元素
    const renderChildren = Children.only(children);

    const pinchZoomRef = useRef<HTMLDivElement | null>(null);
    const positionEleRef = useRef<HTMLDivElement | null>(null);
    const [matrix, setMatrix] = useState<DOMMatrix>(createMatrix());

    const [allowChangeEvent, setAllowChangeEvent] = useState<boolean>(true);

    // const handleTransform = useCallback(
    //   (opts: HandleTransformOpts = {}) => {
    //     const _positioningEl = positionEleRef.current;
    //     const pinchZoom = pinchZoomRef.current;
    //     if (!pinchZoom || !_positioningEl) return;
    //     let { x = matrix.e, y = matrix.f, scale = matrix.a } = opts;

    //     // Get current layout
    //     const thisBounds = pinchZoom.getBoundingClientRect();
    //     const positioningElBounds = _positioningEl.getBoundingClientRect();

    //     // Create points for _positioningEl.
    //     let topLeft = createPoint();
    //     topLeft.x = positioningElBounds.left - thisBounds.left;
    //     topLeft.y = positioningElBounds.top - thisBounds.top;
    //     let bottomRight = createPoint();
    //     bottomRight.x = positioningElBounds.width + topLeft.x;
    //     bottomRight.y = positioningElBounds.height + topLeft.y;

    //     const _matrix = matrix.translate(x, y).scale(scale);

    //     topLeft = topLeft.matrixTransform(_matrix);
    //     bottomRight = bottomRight.matrixTransform(_matrix);

    //     // Correct for x
    //     if (topLeft.x > thisBounds.width) {
    //       x += thisBounds.width - topLeft.x;
    //     } else if (bottomRight.x < 0) {
    //       x += -bottomRight.x;
    //     }

    //     // Correct for y
    //     if (topLeft.y > thisBounds.height) {
    //       y += thisBounds.height - topLeft.y;
    //     } else if (bottomRight.y < 0) {
    //       y += -bottomRight.y;
    //     }

    //     // 修正缩放
    //     scale = Math.max(Math.min(scale, MAX_SCALE), MIN_SCALE);

    //     if (scale === matrix.a && x === matrix.e && y === matrix.f) return;

    //     setAllowChangeEvent(allowChangeEvent);
    //     setMatrix((matrix) => matrix.translate(x, y).scale(scale));
    //   },
    //   [allowChangeEvent, matrix]
    // );

    const _applyChange = useCallback((opts: ApplyChangeOpts = {}) => {
      const {
        panX = 0,
        panY = 0,
        scaleDiff = 1,
        allowChangeEvent = false,
      } = opts;

      setAllowChangeEvent(allowChangeEvent);
      setMatrix((matrix) =>
        matrix
          .translate(panX, panY)
          .scale(Math.max(Math.min(scaleDiff, MAX_SCALE), MIN_SCALE))
      );
    }, []);

    const _rotate = useCallback((rotation: number) => {
      const pinchZoom = pinchZoomRef.current;
      if (!pinchZoom) return;
      pinchZoom.style.setProperty('--rotate', rotation + 'deg');
    }, []);

    const _onScale = useCallback((scale: number) => {
      setMatrix((matrix) => matrix.scale(scale));
    }, []);

    // 处理移动
    const _onPointerMove = useCallback(
      (pp: Pointer[], cp: Pointer[]) => {
        const _positioningEl = positionEleRef.current;
        if (!_positioningEl) return;

        const currentRect = _positioningEl.getBoundingClientRect();
        const prevMidpoint = getMidpoint(pp[0], pp[1]);
        const newMidpoint = getMidpoint(cp[0], cp[1]);

        const originX = prevMidpoint.clientX - currentRect.left;
        const originY = prevMidpoint.clientY - currentRect.top;

        const prevDistance = getDistance(pp[0], pp[1]);
        const newDistance = getDistance(cp[0], cp[1]);
        const scaleDiff = prevDistance ? newDistance / prevDistance : 1;

        _applyChange({
          originX,
          originY,
          scaleDiff,
          panX: newMidpoint.clientX - prevMidpoint.clientX,
          panY: newMidpoint.clientY - prevMidpoint.clientY,
          allowChangeEvent: true,
        });
      },
      [_applyChange]
    );

    // 处理缩放
    const _onWheel = useCallback(
      (event: WheelEvent) => {
        const _positioningEl = positionEleRef.current;
        if (!_positioningEl) return;

        const currentRect = _positioningEl.getBoundingClientRect();
        let { deltaY } = event;
        const { ctrlKey, deltaMode } = event;

        if (deltaMode === 1) {
          deltaY *= 15;
        }

        const zoomingOut = deltaY > 0;

        const divisor = ctrlKey ? 100 : 300;
        const ratio = 1 - (zoomingOut ? -deltaY : deltaY) / divisor;
        const scaleDiff = zoomingOut ? 1 / ratio : ratio;

        _applyChange({
          scaleDiff,
          originX: event.clientX - currentRect.left,
          originY: event.clientY - currentRect.top,
          allowChangeEvent: true,
        });
      },
      [_applyChange]
    );

    useEffect(() => {
      const pinchZoom = pinchZoomRef.current;
      if (!pinchZoom) return;
      const { a: _scale, e: _x, f: _y } = matrix;

      pinchZoom.style.setProperty('--x', _x + 'px');
      pinchZoom.style.setProperty('--y', _y + 'px');
      pinchZoom.style.setProperty('--scale', _scale + '');

      if (onChange && allowChangeEvent) {
        onChange();
      }
    }, [allowChangeEvent, onChange, matrix]);

    useImperativeHandle(
      forwardedRef,
      () => {
        return {
          _onPointerMove,
          _onWheel,
          _rotate,
          _onScale,
        };
      },
      [_onWheel, _onPointerMove, _rotate, _onScale]
    );

    return (
      <Box
        className="pinch-zoom"
        ref={pinchZoomRef}
        sx={{
          overflow: 'hidden',
          touchAction: 'none',
          position: 'absolute',
          display: 'flex',
          justifyContent: 'center',
          alignItems: 'center',
          top: '0',
          left: '0',
          width: '100%',
          height: '100%',
          boxSizing: 'border-box',
          contain: 'strict',
          '--scale': '1',
          '--x': '0px',
          '--y': '0px',
          '--rotate': '0deg',
          '& > *': {
            transform:
              'translate(var(--x), var(--y)) scale(var(--scale)) rotate(var(--rotate))',
            transformOrigin: 'center',
            willChange: 'transform',
          },
        }}
      >
        <Box ref={positionEleRef}>{renderChildren}</Box>
      </Box>
    );
  }
);

export default memo(PinchZoom);
